/***************************************************************************
 *   Copyright (C) 2004 by Tyler Montbriand, tsm@accesscomm.ca             *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************
 *      SDL_rwring -- A threadsafe ring-buffer using SDL and SDL_RWops     *
 ***************************************************************************/

#include <stdlib.h>
#include <string.h>

#include <SDL/SDL_types.h>
#include <SDL/SDL_thread.h>
#include <SDL/SDL_error.h>
#include "SDL_rwring.h"
#include "SDL_rwlib_internal.h"

#define PKGNAME RING

typedef struct ringbuf
{
  int length, blocksize,timeout;
  Uint8 *buffer;
  Uint32 rdpos,wrpos;
  SDL_sem *rd,*wr;
} ringbuf;

RWFUNC_SEEK(PKGNAME);
RWFUNC_READ(PKGNAME);
RWFUNC_WRITE(PKGNAME);
RWFUNC_CLOSE(PKGNAME);

void my_memcpy(void *dest, const void *src, Uint32 len)
{
  fprintf(stderr,"my_memcpy(%p,%p,%u)\n",dest,src,len);
  memcpy(dest,src,len);
}

static void ring_free(ringbuf *rb);

SDL_RWops *SDL_RWringbuf(int size,int blocksize, int timeout)
{
  ringbuf   *rb=NULL;
  SDL_RWops *rw=NULL;

  if((size<=0)||(blocksize<=0))
  {
    SDL_SetError("ringbuf: size or blocksize too small");
    goto RING_ALLOC_ERROR;
  }
  else if((size%blocksize)!=0)
  {
    SDL_SetError("ringbuf: size not a multiple of blocksize");
    goto RING_ALLOC_ERROR;
  }
  else if((size/blocksize)<2)
  {
    SDL_SetError("ringbuf: size not at least 2x blocksize");
    goto RING_ALLOC_ERROR;
  }

  rw=SDL_AllocRW();

  if(rw==NULL)
  {
    SDL_SetError("ringbuf: couldn't allocate RWops structure");
    goto RING_ALLOC_ERROR;
  }
  memset(rw,0,sizeof(SDL_RWops));
  RW_INITFUNC(rw,PKGNAME);

  rb=(ringbuf *)malloc(sizeof(ringbuf));
  if(rb==NULL)
  {
    SDL_SetError("ringbuf:  couldn't allocate buffer structure");
    goto RING_ALLOC_ERROR;
  }
  memset(rb,0,sizeof(ringbuf));

  rb->length=size;
  rb->blocksize=blocksize;
  rb->timeout=timeout;
  rb->rdpos=0;
  rb->wrpos=0;

  rb->buffer=(Uint8 *)malloc(size);
  if(rb->buffer==NULL)
  {
    SDL_SetError("ringbuf:  couldn't allocate data buffer");
    goto RING_ALLOC_ERROR;
  }
  memset(rb->buffer,0,size);

  rb->rd=SDL_CreateSemaphore(0);
  if(rb->rd==NULL)
  {
    SDL_SetError("ringbuf:  couldn't allocate read semaphore");
    goto RING_ALLOC_ERROR;
  }

  rb->wr=SDL_CreateSemaphore(size/blocksize);
  if(rb->wr==NULL)
  {
    SDL_SetError("ringbuf:  couldn't alloc write semaphore");
    goto RING_ALLOC_ERROR;
  }

  RW_DATAPTR1(rw)=(void *)rb;
  return(rw);  

RING_ALLOC_ERROR:
  if(rw!=NULL) SDL_FreeRW(rw);
  if(rb!=NULL) ring_free(rb);

  return(NULL);  
}

// Seeking not implemented in a ring buffer!
RWFUNC_SEEK_STUB(PKGNAME);

RWFUNC_READ(PKGNAME)
  /*(SDL_RWops *ctx, void *ptr, int size, int maxnum)*/
{
  Uint8 *dptr=(Uint8 *)ptr;
  int blocks=0;

  ringbuf *rb=(ringbuf *)RW_DATAPTR1(ctx);

  if((size<rb->blocksize)||((size%rb->blocksize)!=0))
  {
    SDL_SetError("ring_read:  size is not a multiple of blocksize");
    return(-1);
  }

  maxnum*=(size/rb->blocksize);

  while(blocks<maxnum)
  {
    if(rb->timeout<0)
    {
      if(SDL_SemWait(rb->rd)<0)
      {
        SDL_SetError("ring_read:  error waiting on sem");
        return(-1);
      }
    }
    else if(SDL_SemWaitTimeout(rb->rd,rb->timeout)!=0)
    {
      if(blocks==0)
        SDL_SetError("ring_read:  semaphore timed out");

      return((blocks*rb->blocksize)/size);
    }

    memcpy(dptr+(blocks*rb->blocksize),rb->buffer+rb->rdpos,rb->blocksize);
    rb->rdpos=(rb->rdpos+rb->blocksize)%rb->length;
    SDL_SemPost(rb->wr);
    blocks++;
  }
  
  return((blocks*rb->blocksize)/size);
}

RWFUNC_WRITE(PKGNAME)
  /*(SDL_RWops *ctx, const void *ptr, int size, int maxnum)*/
{
  Uint8 *dptr=(Uint8 *)ptr;
  int blocks=0;
  ringbuf *rb=(ringbuf *)RW_DATAPTR1(ctx);

  if((size<rb->blocksize)||((size%rb->blocksize)!=0))
  {
    SDL_SetError("ring_write:  size is not a multiple of blocksize");
    return(-1);
  }

  maxnum*=(size/rb->blocksize);

  while(blocks<maxnum)
  {
    if(rb->timeout<0)
    {
      if(SDL_SemWait(rb->wr)<0)
      {
        SDL_SetError("ring_read:  error waiting on sem");
        return(-1);
      }
    }
    else if(SDL_SemWaitTimeout(rb->wr,rb->timeout)!=0)
    {
      if(blocks==0)
        SDL_SetError("ring_read:  semaphore timed out");

      return((blocks*rb->blocksize)/size);
    }

    memcpy(rb->buffer+rb->wrpos,dptr+(blocks*rb->blocksize),rb->blocksize);
    rb->wrpos=(rb->wrpos+rb->blocksize)%rb->length;
    SDL_SemPost(rb->rd);
    blocks++;
  }
  
  return((blocks*rb->blocksize)/size);
}

RWFUNC_CLOSE(PKGNAME)
  /*(SDL_RWops *ctx)*/
{
  if(ctx==NULL) return(-1);
  else if(RW_TYPE(ctx)!=RW_TYPENUM(PKGNAME))
  {
    SDL_SetError("ring_close:  not a ring buffer!");
    return(-1);
  }

  ring_free((ringbuf *)RW_DATAPTR1(ctx));
  SDL_FreeRW(ctx);
  return(0);
}

static void ring_free(ringbuf *rb)
{
  if(rb==NULL) return;

  if(rb->buffer!=NULL) 
  if(rb->rd!=NULL)     SDL_DestroySemaphore(rb->rd);
  if(rb->wr!=NULL)     SDL_DestroySemaphore(rb->wr);

  free(rb);
}

int SDL_RWringbuf_empty(SDL_RWops *ctx)
{
  if(ctx==NULL) return(-1);
  else if(RW_TYPE(ctx) != RW_TYPENUM(PKGNAME))
  {
    SDL_SetError("SDL_RWringbuf_empty:  Not a ring buffer!");
    return(-1);
  }
  else
  {
    ringbuf *r=(ringbuf *)RW_DATAPTR1(ctx);
    return(SDL_SemValue(r->rd) == 0);
  }
}
